
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>

typedef char ***base_reg_t;
typedef struct cthread cthread_t;
struct cthread
{
    jmp_buf jb;
    base_reg_t stack_origin;
    base_reg_t stack;
    size_t stacksize;
    void (*entry)(void *arg);
    void *arg;
};

cthread_t tha, thb;


typedef struct system_attr system_attr_t;
struct system_attr
{
    uint8_t is_stack_godown;
    uint8_t n;
    uint16_t mybe_stackreg_in_jumpbuffer[32];
};

system_attr_t g_system_attr;

void cthread_init( cthread_t *p_this, size_t stacksize, void (*entry)(void*), void *arg )
{
    memset(p_this, 0x00, sizeof(*p_this));
    p_this->stacksize = stacksize;
    p_this->entry = entry;
    p_this->arg = arg;
    p_this->stack_origin = (base_reg_t)malloc( stacksize );
    if (g_system_attr.is_stack_godown){
        int s = sizeof(base_reg_t);
        p_this->stack = p_this->stack_origin + (p_this->stacksize/s) - 1;
    } else {
        p_this->stack = p_this->stack_origin;
    }
    memset( p_this->stack_origin, 0x00, p_this->stacksize );
}

void show_mem( base_reg_t p, int n, const char *note )
{
    printf("\n[%s]\n", note );
    for (int i=0; i<n; i++){
        if (i % 4 == 0)printf("\n");
        printf(" [%p]=%p", p, *p);
        p++;
    }
    printf("\n");
}

void cthread_stack_scroll_do(cthread_t* p_this, int index, base_reg_t* parent);

#define STACK_SCROLL_TIMES 3

void cthread_stack_scroll(cthread_t* p_this )
{
    base_reg_t ptr[STACK_SCROLL_TIMES] = { 0 };
    memset( &ptr, 0x00, 256 );
    printf("========== &array=%p p_this=%p ==========\n", ptr, p_this );
    cthread_stack_scroll_do(p_this, 0, ptr);
}
void cthread_stack_scroll_do( cthread_t *p_this, int index, base_reg_t *parent)
{
    base_reg_t pa = 0;
    base_reg_t pi = 0;
    base_reg_t pstart = 0;
    base_reg_t pend = 0;
    base_reg_t pb = 0;
    base_reg_t v = 0;
    union __local
    {
        int i;
        base_reg_t d;
    }len, offset;
    base_reg_t ptr = 0;

    len.d = 0;
    offset.d = 0;

    if (index >= STACK_SCROLL_TIMES){
        if (g_system_attr.is_stack_godown){
            pa = parent[STACK_SCROLL_TIMES-1];
            pb = parent[0];
            len.i = (pb - pa);
            pstart = p_this->stack - len.i;
            pend = p_this->stack;
            memcpy( pstart, pa, len.i*sizeof(base_reg_t) );
        } else {
            pa = parent[0];
            pb = parent[STACK_SCROLL_TIMES - 1];
            len.i = pb - pa;
            pstart = p_this->stack;
            pend = p_this->stack + len.i;
            memcpy( p_this->stack, pa, len.i*sizeof(base_reg_t) );
        }
        printf( " memcopy (%p, %p) -> (%p, %p) p_this=%p len=%d size=%d fun=%p\n", pa, pb, pstart, pend, p_this, len.i, len.i*sizeof(base_reg_t), cthread_stack_scroll_do );
        #if 1
        pi = pstart;
        show_mem(pa, pb-pa+1, "origin stack");
        show_mem(pstart, pend-pstart+1, "new thread stack before changing");
        while ( pi <= pend ){
            v = (base_reg_t)*pi;
            if ( v > pa && v < pb && v != (base_reg_t)p_this) {
                *pi = (char*)(pend - ( pb - v));
                printf("changed value @%p %p -> %p diff=%d\n", pi, v, *pi, (pb-v) );
            } else if (v == (base_reg_t)parent){
                *pi = NULL;
                printf("cleared value @%p %p -> %p diff=%d\n", pi, v, *pi, (pb-v) );
            }
            pi++;
        }
        show_mem(pstart, pend-pstart+1, "new thread stack after changing");
        #endif
        pi = (base_reg_t)&p_this->jb;
        pend = pi + sizeof(p_this->jb)/sizeof(pi);
        show_mem(pi, pend-pi+1, "jmp_buf before changing");
        for (len.i=0; len.i<g_system_attr.n; len.i++){
            offset.i = g_system_attr.mybe_stackreg_in_jumpbuffer[len.i];
            v = (base_reg_t)pi[offset.i];
            if ( v > pa && v < pb && v != (base_reg_t)p_this){
                pi[offset.i] = p_this->stack - ( pb - v );
                printf("JB changed value @[%d] %p -> %p diff=%d\n", offset.i, v, *pi, (pb - v));
            }
        }
        show_mem(pi, pend-pi+1, "jmp_buf after changing");
        return ;
    }
    if (index == 2){
        int ret = setjmp( p_this->jb );
        if (ret){
            p_this->entry( p_this->arg );
            while (1){
                printf("thread ended\n");
            }
        }
    }
    parent[index] = (base_reg_t)&ptr;
    printf("========== &array[%d]=%p %p ==========\n", index, parent[index], &ptr);
    cthread_stack_scroll_do(p_this, index+1, parent );
}



struct find_stack_helper
{
    jmp_buf buf[3];
};
typedef struct find_stack_helper find_stack_helper_t;

void find_stack( find_stack_helper_t *p_this, int index, const char **pp)
{
    const char *ptr = 0;
    const char **p0 = &ptr;
    const char **p1 = pp;
    setjmp( p_this->buf[index] );
    if (index >= 2){
        return;
    }
    find_stack(p_this, index + 1, &ptr );
}

void system_attr_detect()
{
    find_stack_helper_t helper;
    find_stack( &helper, 0, NULL );

    char **ptr  = (char**) &helper.buf[0];
    char **ptr2 = (char**) &helper.buf[1];
    char **ptr3 = (char**) &helper.buf[2];
    for (int i=0; i<sizeof(jmp_buf)/sizeof(char*); i++){
        int diff1 = (*ptr2-*ptr);
        int diff2 = (*ptr3-*ptr2);
        const char *strflag = "";
        if (diff1 && (diff1 == diff2)){
            if (diff1 < 0){
                g_system_attr.is_stack_godown = 1;
                g_system_attr.mybe_stackreg_in_jumpbuffer[g_system_attr.n++] = i;
                strflag = "****";
            } else {
                g_system_attr.is_stack_godown = 0;
                g_system_attr.mybe_stackreg_in_jumpbuffer[g_system_attr.n++] = i;
                strflag = "****";
            }
        }
        printf("buffer[%2d]=%011p %d %011p %d %011p %s\n", i, *ptr, diff1, *ptr2, diff2, *ptr3, strflag );
        ptr++;
        ptr2++;
        ptr3++;
    }
    printf("\n");
}

void entrya(void *a)
{
    int i=0;
    while (1){
        printf("thread a i=%d\n", i);
        int a = setjmp(tha.jb);
        if (a == 0) {
            longjmp(thb.jb, &thb);
        }
    }
}

void entryb(void *a)
{
    int i=0;
    while (1){
        printf("thread b i=%d\n", i);
        int a = setjmp(thb.jb);
        if (a == 0) {
            longjmp(tha.jb, &tha);
        }
    }
}
int main()
{
    printf("sizeof(char*)=%d\n", sizeof(char*));

    system_attr_detect();

    cthread_init(&tha, 0x100000, entrya, 0);
    cthread_init(&thb, 0x100000, entryb, 0);

    cthread_stack_scroll(&tha);
    cthread_stack_scroll(&thb);

    while (1){
        printf("main thread\n");
        longjmp( tha.jb, &tha );
    }

    return 0;
}

